/**
 * Copyright (c) 2013-2020 Contributors to the Eclipse Foundation
 *
 * <p> See the NOTICE file distributed with this work for additional information regarding copyright
 * ownership. All rights reserved. This program and the accompanying materials are made available
 * under the terms of the Apache License, Version 2.0 which accompanies this distribution and is
 * available at http://www.apache.org/licenses/LICENSE-2.0.txt
 */
/*
 * JAI-Ext - OpenSource Java Advanced Image Extensions Library http://www.geo-solutions.it/
 * Copyright 2014 GeoSolutions Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You may obtain a copy of the License
 * at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in
 * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */
package org.locationtech.geowave.adapter.raster.adapter.warp;

import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.util.Map;
import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.RasterAccessor;
import javax.media.jai.Warp;
import javax.media.jai.iterator.RandomIter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import it.geosolutions.jaiext.iterators.RandomIterFactory;
import it.geosolutions.jaiext.range.Range;

/**
 * This is code entirely intended to get around an issue on line 265 of WarpOpImage in jai-ext. The
 * following code does not work if the source is significant lower resolution than the destination
 * and seems unnecessary in general:
 *
 * <p> roiTile = roi.intersect(new ROIShape(srcRectExpanded));
 *
 * <p> An <code>OpImage</code> implementing the general "Warp" operation as described in <code>
 * javax.media.jai.operator.WarpDescriptor</code>. It supports the nearest-neighbor interpolation.
 *
 * <p> The layout for the destination image may be specified via the <code>ImageLayout</code>
 * parameter. However, only those settings suitable for this operation will be used. The unsuitable
 * settings will be replaced by default suitable values. An optional ROI object and a NoData Range
 * can be used. If a backward mapped pixel lies outside ROI or it is a NoData, then the destination
 * pixel value is a background value.
 *
 * @since EA2
 * @see javax.media.jai.Warp
 * @see javax.media.jai.WarpOpImage
 * @see javax.media.jai.operator.WarpDescriptor
 * @see WarpRIF
 */
@SuppressWarnings("unchecked")
@SuppressFBWarnings
final class WarpNearestOpImage extends WarpOpImage {
  /** LookupTable used for a faster NoData check */
  private byte[][] byteLookupTable;

  /**
   * Constructs a WarpNearestOpImage.
   *
   * @param source The source image.
   * @param config RenderingHints used in calculations.
   * @param layout The destination image layout.
   * @param warp An object defining the warp algorithm.
   * @param interp An object describing the interpolation method.
   * @param roi input ROI object used.
   * @param noData NoData Range object used for checking if NoData are present.
   */
  public WarpNearestOpImage(
      final RenderedImage source,
      final Map<?, ?> config,
      final ImageLayout layout,
      final Warp warp,
      final Interpolation interp,
      final ROI sourceROI,
      final Range noData,
      final double[] bkg) {
    super(
        source,
        layout,
        config,
        false,
        null, // extender not needed in
        // nearest-neighbor
        // interpolation
        interp,
        warp,
        bkg,
        sourceROI,
        noData);

    /*
     * If the source has IndexColorModel, override the default setting in OpImage. The dest shall
     * have exactly the same SampleModel and ColorModel as the source. Note, in this case, the
     * source should have an integral data type.
     */
    final ColorModel srcColorModel = source.getColorModel();
    if (srcColorModel instanceof IndexColorModel) {
      sampleModel = source.getSampleModel().createCompatibleSampleModel(tileWidth, tileHeight);
      colorModel = srcColorModel;
    }

    /*
     * Selection of a destinationNoData value for each datatype
     */
    final SampleModel sm = source.getSampleModel();
    // Source image data Type
    final int srcDataType = sm.getDataType();

    // Creation of a lookuptable containing the values to use for no data
    if ((srcDataType == DataBuffer.TYPE_BYTE) && hasNoData) {
      final int numBands = getNumBands();
      byteLookupTable = new byte[numBands][256];
      for (int b = 0; b < numBands; b++) {
        for (int i = 0; i < byteLookupTable[0].length; i++) {
          final byte value = (byte) i;
          if (noDataRange.contains(value)) {
            byteLookupTable[b][i] = (byte) backgroundValues[b];
          } else {
            byteLookupTable[b][i] = value;
          }
        }
      }
    }
  }

  @Override
  protected void computeRectByte(
      final PlanarImage src,
      final RasterAccessor dst,
      final RandomIter roiIter,
      final boolean roiContainsTile) {
    // Random Iterator on the source image bounds
    final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
    // Initial settings
    final int minX = src.getMinX();
    final int maxX = src.getMaxX();
    final int minY = src.getMinY();
    final int maxY = src.getMaxY();

    final int dstWidth = dst.getWidth();
    final int dstHeight = dst.getHeight();
    final int dstBands = dst.getNumBands();

    final int lineStride = dst.getScanlineStride();
    final int pixelStride = dst.getPixelStride();
    final int[] bandOffsets = dst.getBandOffsets();
    final byte[][] data = dst.getByteDataArrays();

    final float[] warpData = new float[2 * dstWidth];

    int lineOffset = 0;

    // NO ROI AND NODATA
    if (caseA || (caseB && roiContainsTile)) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);
          // If the pixel is outside the input image bounds
          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
              }
            }
          } else {
            // Nearest interpolation
            for (int b = 0; b < dstBands; b++) {
              data[b][pixelOffset + bandOffsets[b]] = (byte) (iter.getSample(sx, sy, b) & 0xFF);
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY ROI
    } else if (caseB) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
                }
              }
            } else {
              // Else the related source pixel is set
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (byte) (iter.getSample(sx, sy, b) & 0xFF);
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY NODATA
    } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
              }
            }
          } else {
            // The related source pixel is set if it isn't a nodata
            for (int b = 0; b < dstBands; b++) {
              data[b][pixelOffset + bandOffsets[b]] = byteLookupTable[b][iter.getSample(sx, sy, b)];
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // BOTH ROI AND NODATA
    } else {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy)
                && roiBounds.contains(sx, sy)
                && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
                }
              }
            } else {
              // The related source pixel is set if it isn't a
              // nodata
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] =
                    byteLookupTable[b][iter.getSample(sx, sy, b)];
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
    }
    iter.done();
  }

  @Override
  protected void computeRectUShort(
      final PlanarImage src,
      final RasterAccessor dst,
      final RandomIter roiIter,
      final boolean roiContainsTile) {
    // Random Iterator on the source image bounds
    final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
    // Initial settings
    final int minX = src.getMinX();
    final int maxX = src.getMaxX();
    final int minY = src.getMinY();
    final int maxY = src.getMaxY();

    final int dstWidth = dst.getWidth();
    final int dstHeight = dst.getHeight();
    final int dstBands = dst.getNumBands();

    final int lineStride = dst.getScanlineStride();
    final int pixelStride = dst.getPixelStride();
    final int[] bandOffsets = dst.getBandOffsets();
    final short[][] data = dst.getShortDataArrays();

    final float[] warpData = new float[2 * dstWidth];

    int lineOffset = 0;

    // NO ROI AND NODATA
    if (caseA || (caseB && roiContainsTile)) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);
          // If the pixel is outside the input image bounds
          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              }
            }
          } else {
            // Nearest interpolation
            for (int b = 0; b < dstBands; b++) {
              data[b][pixelOffset + bandOffsets[b]] = (short) (iter.getSample(sx, sy, b) & 0xFFFF);
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY ROI
    } else if (caseB) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy)
                && roiBounds.contains(sx, sy)
                && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                }
              }
            } else {
              // Else the related source pixel is set
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] =
                    (short) (iter.getSample(sx, sy, b) & 0xFFFF);
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY NODATA
    } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
      short inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              }
            }
          } else {
            // The related source pixel is set if it isn't a nodata
            for (int b = 0; b < dstBands; b++) {
              // Input value selected
              inputValue = (short) (iter.getSample(sx, sy, b) & 0xFFFF);
              if (noDataRange.contains(inputValue)) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              } else {
                data[b][pixelOffset + bandOffsets[b]] = inputValue;
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // BOTH ROI AND NODATA
    } else {
      short inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                }
              }
            } else {
              // The related source pixel is set if it isn't a
              // nodata
              for (int b = 0; b < dstBands; b++) {
                // Input value selected
                inputValue = (short) (iter.getSample(sx, sy, b) & 0xFFFF);
                if (noDataRange.contains(inputValue)) {
                  data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                } else {
                  data[b][pixelOffset + bandOffsets[b]] = inputValue;
                }
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
    }
    iter.done();
  }

  @Override
  protected void computeRectShort(
      final PlanarImage src,
      final RasterAccessor dst,
      final RandomIter roiIter,
      final boolean roiContainsTile) {
    // Random Iterator on the source image bounds
    final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
    // Initial settings
    final int minX = src.getMinX();
    final int maxX = src.getMaxX();
    final int minY = src.getMinY();
    final int maxY = src.getMaxY();

    final int dstWidth = dst.getWidth();
    final int dstHeight = dst.getHeight();
    final int dstBands = dst.getNumBands();

    final int lineStride = dst.getScanlineStride();
    final int pixelStride = dst.getPixelStride();
    final int[] bandOffsets = dst.getBandOffsets();
    final short[][] data = dst.getShortDataArrays();

    final float[] warpData = new float[2 * dstWidth];

    int lineOffset = 0;

    // NO ROI AND NODATA
    if (caseA || (caseB && roiContainsTile)) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);
          // If the pixel is outside the input image bounds
          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              }
            }
          } else {
            // Nearest interpolation
            for (int b = 0; b < dstBands; b++) {
              data[b][pixelOffset + bandOffsets[b]] = (short) iter.getSample(sx, sy, b);
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY ROI
    } else if (caseB) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                }
              }
            } else {
              // Else the related source pixel is set
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) iter.getSample(sx, sy, b);
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY NODATA
    } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
      short inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              }
            }
          } else {
            // The related source pixel is set if it isn't a nodata
            for (int b = 0; b < dstBands; b++) {
              // Input value selected
              inputValue = (short) iter.getSample(sx, sy, b);
              if (noDataRange.contains(inputValue)) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              } else {
                data[b][pixelOffset + bandOffsets[b]] = inputValue;
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // BOTH ROI AND NODATA
    } else {
      short inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                }
              }
            } else {
              // The related source pixel is set if it isn't a
              // nodata
              for (int b = 0; b < dstBands; b++) {
                // Input value selected
                inputValue = (short) iter.getSample(sx, sy, b);
                if (noDataRange.contains(inputValue)) {
                  data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                } else {
                  data[b][pixelOffset + bandOffsets[b]] = inputValue;
                }
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
    }
    iter.done();
  }

  @Override
  protected void computeRectInt(
      final PlanarImage src,
      final RasterAccessor dst,
      final RandomIter roiIter,
      final boolean roiContainsTile) {
    // Random Iterator on the source image bounds
    final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
    // Initial settings
    final int minX = src.getMinX();
    final int maxX = src.getMaxX();
    final int minY = src.getMinY();
    final int maxY = src.getMaxY();

    final int dstWidth = dst.getWidth();
    final int dstHeight = dst.getHeight();
    final int dstBands = dst.getNumBands();

    final int lineStride = dst.getScanlineStride();
    final int pixelStride = dst.getPixelStride();
    final int[] bandOffsets = dst.getBandOffsets();
    final int[][] data = dst.getIntDataArrays();

    final float[] warpData = new float[2 * dstWidth];

    int lineOffset = 0;

    // NO ROI AND NODATA
    if (caseA || (caseB && roiContainsTile)) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);
          // If the pixel is outside the input image bounds
          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
              }
            }
          } else {
            // Nearest interpolation
            for (int b = 0; b < dstBands; b++) {
              data[b][pixelOffset + bandOffsets[b]] = iter.getSample(sx, sy, b);
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY ROI
    } else if (caseB) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                }
              }
            } else {
              // Else the related source pixel is set
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = iter.getSample(sx, sy, b);
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY NODATA
    } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
      int inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
              }
            }
          } else {
            // The related source pixel is set if it isn't a nodata
            for (int b = 0; b < dstBands; b++) {
              // Input value selected
              inputValue = iter.getSample(sx, sy, b);
              if (noDataRange.contains(inputValue)) {
                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
              } else {
                data[b][pixelOffset + bandOffsets[b]] = inputValue;
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // BOTH ROI AND NODATA
    } else {
      int inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                }
              }
            } else {
              // The related source pixel is set if it isn't a
              // nodata
              for (int b = 0; b < dstBands; b++) {
                // Input value selected
                inputValue = iter.getSample(sx, sy, b);
                if (noDataRange.contains(inputValue)) {
                  data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                } else {
                  data[b][pixelOffset + bandOffsets[b]] = inputValue;
                }
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
    }
    iter.done();
  }

  @Override
  protected void computeRectFloat(
      final PlanarImage src,
      final RasterAccessor dst,
      final RandomIter roiIter,
      final boolean roiContainsTile) {
    // Random Iterator on the source image bounds
    final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
    // Initial settings
    final int minX = src.getMinX();
    final int maxX = src.getMaxX();
    final int minY = src.getMinY();
    final int maxY = src.getMaxY();

    final int dstWidth = dst.getWidth();
    final int dstHeight = dst.getHeight();
    final int dstBands = dst.getNumBands();

    final int lineStride = dst.getScanlineStride();
    final int pixelStride = dst.getPixelStride();
    final int[] bandOffsets = dst.getBandOffsets();
    final float[][] data = dst.getFloatDataArrays();

    final float[] warpData = new float[2 * dstWidth];

    int lineOffset = 0;

    // NO ROI AND NODATA
    if (caseA || (caseB && roiContainsTile)) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);
          // If the pixel is outside the input image bounds
          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
              }
            }
          } else {
            // Nearest interpolation
            for (int b = 0; b < dstBands; b++) {
              data[b][pixelOffset + bandOffsets[b]] = iter.getSampleFloat(sx, sy, b);
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY ROI
    } else if (caseB) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                }
              }
            } else {
              // Else the related source pixel is set
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = iter.getSampleFloat(sx, sy, b);
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY NODATA
    } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
      float inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
              }
            }
          } else {
            // The related source pixel is set if it isn't a nodata
            for (int b = 0; b < dstBands; b++) {
              // Input value selected
              inputValue = iter.getSampleFloat(sx, sy, b);
              if (noDataRange.contains(inputValue)) {
                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
              } else {
                data[b][pixelOffset + bandOffsets[b]] = inputValue;
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // BOTH ROI AND NODATA
    } else {
      float inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                }
              }
            } else {
              // The related source pixel is set if it isn't a
              // nodata
              for (int b = 0; b < dstBands; b++) {
                // Input value selected
                inputValue = iter.getSampleFloat(sx, sy, b);
                if (noDataRange.contains(inputValue)) {
                  data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                } else {
                  data[b][pixelOffset + bandOffsets[b]] = inputValue;
                }
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
    }
    iter.done();
  }

  @Override
  protected void computeRectDouble(
      final PlanarImage src,
      final RasterAccessor dst,
      final RandomIter roiIter,
      final boolean roiContainsTile) {
    // Random Iterator on the source image bounds
    final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
    // Initial settings
    final int minX = src.getMinX();
    final int maxX = src.getMaxX();
    final int minY = src.getMinY();
    final int maxY = src.getMaxY();

    final int dstWidth = dst.getWidth();
    final int dstHeight = dst.getHeight();
    final int dstBands = dst.getNumBands();

    final int lineStride = dst.getScanlineStride();
    final int pixelStride = dst.getPixelStride();
    final int[] bandOffsets = dst.getBandOffsets();
    final double[][] data = dst.getDoubleDataArrays();

    final float[] warpData = new float[2 * dstWidth];

    int lineOffset = 0;

    // NO ROI AND NODATA
    if (caseA || (caseB && roiContainsTile)) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);
          // If the pixel is outside the input image bounds
          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
              }
            }
          } else {
            // Nearest interpolation
            for (int b = 0; b < dstBands; b++) {
              data[b][pixelOffset + bandOffsets[b]] = iter.getSampleDouble(sx, sy, b);
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY ROI
    } else if (caseB) {
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                }
              }
            } else {
              // Else the related source pixel is set
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = iter.getSampleDouble(sx, sy, b);
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // ONLY NODATA
    } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
      double inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
              }
            }
          } else {
            // The related source pixel is set if it isn't a nodata
            for (int b = 0; b < dstBands; b++) {
              // Input value selected
              inputValue = iter.getSampleDouble(sx, sy, b);
              if (noDataRange.contains(inputValue)) {
                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
              } else {
                data[b][pixelOffset + bandOffsets[b]] = inputValue;
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
      // BOTH ROI AND NODATA
    } else {
      double inputValue = 0;
      for (int h = 0; h < dstHeight; h++) {
        int pixelOffset = lineOffset;
        lineOffset += lineStride;
        // Calculation of the warp for the selected row
        warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
        int count = 0;
        for (int w = 0; w < dstWidth; w++) {
          /*
           * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round
           * to get the nearest neighbor. This is different from the standard nearest
           * implementation.
           */
          final int sx = round(warpData[count++]);
          final int sy = round(warpData[count++]);

          if ((sx < minX) || (sx >= maxX) || (sy < minY) || (sy >= maxY)) {
            /* Fill with a background color. */
            if (setBackground) {
              for (int b = 0; b < dstBands; b++) {
                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
              }
            }
          } else {
            // SG if we falls outside the roi we use the background
            // value
            if (!(roiBounds.contains(sx, sy) && (roiIter.getSample(sx, sy, 0) > 0))) {
              /* Fill with a background color. */
              if (setBackground) {
                for (int b = 0; b < dstBands; b++) {
                  data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                }
              }
            } else {
              // The related source pixel is set if it isn't a
              // nodata
              for (int b = 0; b < dstBands; b++) {
                // Input value selected
                inputValue = iter.getSampleDouble(sx, sy, b);
                if (noDataRange.contains(inputValue)) {
                  data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                } else {
                  data[b][pixelOffset + bandOffsets[b]] = inputValue;
                }
              }
            }
          }
          pixelOffset += pixelStride;
        }
      }
    }
    iter.done();
  }
}
